import Button from "@/component/Button";
import { Dropdown } from "@/component/Dropdown";
import { Modal } from "@/component/Modal";
import { Toast } from "@/component/Toast";
import { GlobalStore } from "@/store/GlobalStore";
import Logger from "@fimagine/logger";
import { IRect } from "@fimagine/writeboard";
import classNames from "classnames";
import dayjs from "dayjs";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Space } from "../Main/Space";
import { CameraView } from "./CameraView";
import { DemoView } from "./DemoView";
import { RoomStatus, RTC } from "./RTC";
import { IKeyFrame } from "./RTC/IKeyFrame";
import { IMember } from "./RTC/IMember";
import styles from './style.module.less';
import { ICallbacks } from "./ViewsSolution/Bridging";
import { DockAlign } from "./ViewsSolution/DockAlign";
import { DockType } from "./ViewsSolution/DockType";
import { rect } from "./ViewsSolution/get_distributions";
import { Bridging_HTMLElement } from "./ViewsSolution/platforms/dom/examples/Bridging_HTMLElement";
import { Interval } from "./ViewsSolution/platforms/dom/Interval";
import { Solution } from "./ViewsSolution/Solution";
import { ViewPlace } from "./ViewsSolution/ViewPlace";
import { Input } from "@/component/Input";
import { Text } from "@/gimd";
Logger.Config.currentTime = () => dayjs().format('YYYY-MM-DD HH:mm:ss SSS');

interface KeyFrameDataMap {
  "ViewChanged": { rect: IRect, place: ViewPlace },
  "LayoutChanged": {
    dock_align: DockAlign,
    dock_type: DockType
  }
}
type KeyFrame = IKeyFrame<KeyFrameDataMap>
interface ViewTypeDataMap {
  "CameraView": IMember,
  "Other": any
}
interface ViewData<K extends keyof ViewTypeDataMap = keyof ViewTypeDataMap> {
  id: string,
  type: K,
  detail: ViewTypeDataMap[K],
}


function new_key_frame<K extends keyof KeyFrameDataMap = keyof KeyFrameDataMap>(
  id: string,
  type: K,
  data: KeyFrameDataMap[K],
): KeyFrame {
  const key_frame: KeyFrame = { id, data, type }
  return key_frame;
}

class Bridging extends Bridging_HTMLElement<HTMLElement, ViewData> {
  protected _rtc: RTC<KeyFrameDataMap> | undefined;
  get solution() { return this._solution; }
  get rtc(): RTC<KeyFrameDataMap> | undefined {
    return this._rtc;
  }
  set rtc(v: RTC<KeyFrameDataMap> | undefined) {
    if (this._rtc === v) return;
    if (this._rtc)
      this._rtc.removeEventListener('key_frames', this.handle_key_frames)
    this._rtc = v;
    if (this._rtc)
      this._rtc.addEventListener('key_frames', this.handle_key_frames)
  }

  handle_key_frames = (e: CustomEvent<KeyFrame[]>) => {
    const { _solution: solution } = this;
    if (!solution) return;
    const { detail: key_frames } = e
    for (const key_frame of key_frames) {
      switch (key_frame.type) {
        case "ViewChanged": {
          const data = key_frame.data as KeyFrameDataMap['ViewChanged'];
          const view = document.getElementById(key_frame.id)
          if (!view) return;
          solution.change_view_place(view, data.place)
          solution.set_rect(view, data.rect)
          solution.raise(view)
          break;
        }
        case "LayoutChanged": {
          const data = key_frame.data as KeyFrameDataMap['LayoutChanged'];
          solution.set_dock_align(data.dock_align);
          solution.set_dock_type(data.dock_type);
          break;
        }
      }
    }
    key_frames.find(v => v.type === 'ViewChanged' || v.type === 'LayoutChanged')
    solution.update();
  }
  emit_layout_changed() {
    const { _solution: solution, _rtc: rtc } = this;
    if (!solution || !rtc) return;
    rtc.send_key_frames([new_key_frame("", "LayoutChanged", {
      dock_align: solution.dock_align,
      dock_type: solution.dock_type
    })])
  }

  emit_views_changed(...views: HTMLElement[]) {
    const { _solution: solution, _rtc: rtc } = this;
    if (!solution || !rtc) return;
    const key_frames: KeyFrame[] = [];
    for (const view of views) {
      const info = solution.get_data_by_view(view);
      if (!info) continue;// TODO: WARN HERE

      const place = solution.get_view_place(view);
      const rect = solution.get_view_rect(view);
      if (place === null) continue; // TODO: WARN HERE
      key_frames.push(new_key_frame(
        `${info.type}#${info.id}`,
        "ViewChanged", {
        place, rect,
      }
      ))
    }
    rtc.send_key_frames(key_frames)
  }
  override on_rect_changed(view: HTMLElement, rect: IRect, prev: IRect): void {
    super.on_rect_changed(view, rect, prev);
  }
  override on_drag_end(view: HTMLElement): void {
    super.on_drag_end(view);
    this.emit_views_changed(view);
  }
}

interface IMediaDeviceInfo {
  deviceId: string | null;
  groupId: string;
  kind: MediaDeviceKind;
  label: string;
}

function useDevices(kind: MediaDeviceKind, devices: IMediaDeviceInfo[]): readonly [IMediaDeviceInfo[], IMediaDeviceInfo['deviceId'], React.Dispatch<React.SetStateAction<IMediaDeviceInfo['deviceId']>>] {
  const list = useMemo<IMediaDeviceInfo[]>(() => {
    const info: IMediaDeviceInfo = { deviceId: null, label: '关闭', kind, groupId: '' }
    return [info, ...devices.filter(v => v.kind === kind && v.deviceId)];
  }, [devices, kind]);

  const [selected, setSelected] = useState<IMediaDeviceInfo['deviceId']>(null)
  useEffect(() => {
    const info = list.find(v => v?.deviceId === selected);
    if (!info) setSelected(null)
  }, [list, selected])
  return [list, selected, setSelected];
}

function useEnumerateDevices() {
  const [devices, set_devices] = useState<IMediaDeviceInfo[]>([])
  const [error, set_error] = useState<any>(null)
  const [inited, set_inited] = useState(false)

  const init = useCallback(() => {
    const job = async () => {
      let devices = await navigator.mediaDevices.enumerateDevices();
      set_devices(devices)

      /**  
       * NOTE:
       *  未有权限时，设备列表中拿到空白deviceId, 
       *  此时用getUserMedia来尝试弹出权限窗口，完成后在重新获取设备列表
       *  -Gim
       */
      const video_device = devices.find(v => v.kind === 'videoinput');
      const audio_device = devices.find(v => v.kind === 'audioinput');
      const video_device_exists = video_device && !video_device.deviceId;
      const audio_device_exists = audio_device && !audio_device.deviceId;
      if (!video_device_exists && !audio_device_exists) return;
      const constraints = { video: video_device_exists, audio: audio_device_exists }
      const stream = await navigator.mediaDevices.getUserMedia(constraints)
      for (const t of stream.getTracks()) {
        t.stop(); // 获取后立刻关闭流。
      }
      devices = await navigator.mediaDevices.enumerateDevices()
      set_devices(devices)
    };
    job().catch(set_error).finally(() => set_inited(true));
  }, [])

  useEffect(() => {
    navigator.mediaDevices.enumerateDevices().then(set_devices).catch(set_error);
  }, [inited])

  return [devices, error, inited, init] as const;
}

export function Chatroom(props: React.HTMLAttributes<HTMLDivElement> & { onSolutionChanged?(solution: Solution): void }) {
  const { onSolutionChanged, ..._p } = props;
  const [devices, error, inited, set_init] = useEnumerateDevices();
  useEffect(() => { error && Toast.show('' + error); }, [error]);
  const [dock_type, set_dock_type] = useState(DockType.Left)
  const [dock_align, set_dock_align] = useState(DockAlign.Start)
  const [audio_input_list, audio_input, set_audio_input] = useDevices('audioinput', devices)
  const [video_input_list, video_input, set_video_input] = useDevices('videoinput', devices)
  const [audio_output_list, audio_output, set_audio_output] = useDevices('audiooutput', devices)
  const ref_rtc = useRef<RTC<KeyFrameDataMap>>()
  useMemo(() => {
    ref_rtc.current?.release();
    const ret = new RTC<KeyFrameDataMap>({ host: WS_URL })
    ref_rtc.current = ret;
    return ret;
  }, [])

  const [solution, bridging] = useMemo(() => {
    const bridging = new Bridging()
    bridging.rtc = ref_rtc.current;
    const solution = new Solution<HTMLElement, ViewData, Bridging>(bridging)
      .set_dock_cell_rect(rect(0, 0, 200, 150))
    solution.set_interval(Interval)
    solution.callbacks.add({
      on_dock_align_changed: v => { set_dock_align(v) },
      on_dock_type_changed: v => { set_dock_type(v) },
    })
    return [((window as any).solution) = solution, bridging];
  }, [])

  useEffect(() => {
    onSolutionChanged?.(solution)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [solution])

  const [{ session_id, user_id }] = GlobalStore.useStore();
  const [room_id, set_room_id] = useState('10');

  const join_room = useCallback(async () => {
    const rtc = ref_rtc.current
    if (!rtc) return;
    if (rtc.room_status === RoomStatus.Joined)
      await rtc.exit_room(room_id)
    rtc.join_room('' + user_id, session_id, room_id).catch(e => console.warn(e))
  }, [user_id, session_id, room_id])

  const exit_room = useCallback(() => {
    ref_rtc.current?.exit_room("10")
  }, [])

  useEffect(() => () => {
    ref_rtc.current?.clear_preview_stream()
  }, [])

  useEffect(() => {
    ref_rtc.current?.set_audio_input_device_id(audio_input).catch(e => {
      Toast.show('' + e)
    })
    ref_rtc.current?.set_video_input_device_id(video_input).catch(e => {
      Toast.show('' + e)
    })
  }, [video_input, audio_input, set_audio_input, set_video_input])

  const ref_workspace_zone = useRef<HTMLDivElement>(null);
  const ref_full_zone = useRef<HTMLDivElement>(null);
  const [members, set_members] = useState<IMember[]>(() => [])
  const [views, set_views] = useState<IMember[]>(() => [])
  const element_map = useMemo(() => new Map<IMember, HTMLElement | null>(), []);
  useEffect(() => {
    let changed = false;
    for (const [k, v] of element_map) {
      if (
        members.indexOf(k) >= 0 ||
        views.indexOf(k) >= 0
      ) continue;
      if (!v) continue;
      changed = true;
      solution.del_view(v);
    }
    if (changed) solution.update();
  }, [element_map, members, solution, views])

  useEffect(() => {
    const ele = ref_workspace_zone.current;
    if (!ele) return;
    const on_resize = () => {
      if (0) {
        const { width: w, height: h } = ele.getBoundingClientRect()
        solution.set_full_zone(rect(0, 27, w, h - 27)).update()
      }
      if (1) {
        const { width: w, height: h } = ele.getBoundingClientRect()
        const empty_space_ratio = w / h;
        const target_ratio = 16 / 9;
        let target_w = w;
        let target_h = h;
        let scale = 1;
        if (empty_space_ratio > target_ratio) {
          target_w = target_h * target_ratio;
          scale = target_w / w
        } else {
          target_h = target_w / target_ratio;
          scale = target_h / h
        }
        const ele_full_zone = ref_full_zone.current!
        ele_full_zone.style.top = 0 + 'px';
        ele_full_zone.style.width = target_w + 'px';
        ele_full_zone.style.height = (27 + target_h) + 'px';
        solution
          .set_full_zone(rect(0, 27, target_w, target_h))
          .set_dock_cell_rect(rect(0, 0, 200 * scale, 150 * scale))
          .update()
      }
    }
    const ob = new ResizeObserver(on_resize)
    ob.observe(ele);
    on_resize()
    return () => ob.disconnect();
  }, [ref_workspace_zone, solution])

  useEffect(() => {
    const rtc = ref_rtc.current;
    if (!rtc) return;
    const off = [
      rtc.on('someone_join_room', ev => set_members(old => [...old.filter(v => ev.detail !== v), ev.detail])),
      rtc.on('someone_exit_room', ev => set_members(old => old.filter(v => ev.detail !== v))),
    ]
    return () => off.forEach(v => v())
  }, [])

  const [settings_modal_open, set_settings_modal_open] = useState(false);
  return (
    <div {..._p} className={classNames(styles.chat_room_view, _p.className)} >
      <div className={styles.workspace_zone} ref={ref_workspace_zone}>
        <Space className={styles.top_bar}>

          <Text type="secondary" size='s'>
            房间ID：
            <Input
              value={room_id}
              onChange={e => set_room_id(e.target.value)}
              onBlur={e => set_room_id(e.target.value.trim())} />
          </Text>
          <Button size='s' onClick={join_room}>进入</Button>
          <Button size='s' onClick={exit_room}>退出</Button>
          <Button size='s' onClick={() => set_settings_modal_open(true)}>设置</Button>
          <Modal open={settings_modal_open} title='设置' onClose={() => set_settings_modal_open(false)}>
            <Space vertical align='left' style={{ display: 'block', fontSize: 12 }}>
              <div style={{ display: 'flex', alignItems: 'center' }}>
                视频输入
                <Dropdown.Select
                  triggers={['click']}
                  style={{ flex: 1, textAlign: 'left' }}
                  options={video_input_list.map(v => ({ value: v.deviceId, label: v.label }))}
                  value={video_input}
                  onPointerDown={() => { inited || set_init() }}
                  onChange={v => set_video_input(v || null)} />
              </div>
              <div style={{ display: 'flex', alignItems: 'center' }}>
                音频输入
                <Dropdown.Select
                  triggers={['click']}
                  style={{ flex: 1, textAlign: 'left' }}
                  options={audio_input_list.map(v => ({ value: v.deviceId, label: v.label }))}
                  value={audio_input}
                  onPointerDown={() => inited || set_init()}
                  onChange={v => set_audio_input(v || null)} />
              </div>
              <div style={{ display: 'flex', alignItems: 'center' }}>
                音频输出
                <Dropdown.Select
                  triggers={['click']}
                  style={{ flex: 1, textAlign: 'left' }}
                  options={audio_output_list.map(v => ({ value: v.deviceId, label: v.label }))}
                  value={audio_output}
                  onPointerDown={() => inited || set_init()}
                  onChange={v => set_audio_output(v || null)} />
              </div>
              <div style={{ display: 'flex', alignItems: 'center' }}>
                Dock Type
                <Dropdown.Select
                  triggers={['click']}
                  style={{ flex: 1, textAlign: 'left' }}
                  options={[
                    DockType.Top,
                    DockType.Bottom,
                    DockType.Left,
                    DockType.Right
                  ].map(v => ({ value: v, label: DockType[v] }))}
                  value={dock_type}
                  onChange={v => {
                    solution.set_dock_type(v!).update();
                    bridging.emit_layout_changed();
                  }} />
              </div>
              <div style={{ display: 'flex', alignItems: 'center' }}>
                Dock Type
                <Dropdown.Select
                  triggers={['click']}
                  style={{ flex: 1, textAlign: 'left' }}
                  options={[
                    DockAlign.Start,
                    DockAlign.Center,
                    DockAlign.End,
                  ].map(v => ({ value: v, label: DockAlign[v] }))}
                  value={dock_align}
                  onChange={v => {
                    solution.set_dock_align(v!).update();
                    bridging.emit_layout_changed();
                  }} />
              </div>
            </Space>
          </Modal>
        </Space>
        <div className={styles.dock_zone_indicator} ref={r => solution.set_dock_zone_indicator(r)} />
        <div className={styles.main_zone_indicator} ref={r => solution.set_main_zone_indicator(r)} />
        <div className={styles.main_zone_indicator} ref={r => solution.set_full_zone_indicator(r)} />
        <div className={styles.full_zone} ref={ref_full_zone} />
        {
          members.map((m) => {
            const _ref = (r: HTMLElement | null) => {
              if (m.element)
                return;
              const ele_video = r?.querySelector('video') || null
              m.set_view(ele_video ? { element: ele_video } : null);
              if (!r || element_map.has(m))
                return;
              element_map.set(m, r)
              solution.set_view_data(r, { id: "" + m.info.user_id, type: 'CameraView', detail: m });
              solution.add_view(r).dock(r).update()
            }
            return (
              <CameraView
                key={m.username}
                _ref={_ref}
                id={`CameraView#${m.info.user_id}`}
                container={ref_full_zone.current}
                member={m}
                onDoubleClick={() => {
                  const view = element_map.get(m)
                  if (!view) return;
                  if (solution.is_maximized(view)) {
                    solution.dock(view).update()
                    bridging.emit_views_changed(view)
                  } else {
                    const views = [...solution.floating_views, view]
                    solution.maximize(...views).update()
                    bridging.emit_views_changed(...views)
                  }
                }}
              />
            )
          })
        }
      </div>
    </div>
  )
}

class DemoCallbacks implements ICallbacks<HTMLElement> {
  solutions: Solution<HTMLElement, any>[];
  constructor(solutions: Solution<HTMLElement>[]) {
    this.solutions = solutions;
  }
  on_add(view: HTMLElement, solution: Solution<HTMLElement>): void {
    for (const s of this.solutions) {
      if (s === solution) continue;
    }
    console.log('on_add')
  }
  on_del(view: HTMLElement, solution: Solution<HTMLElement>): void {
    console.log('on_del')
  }
}

export default function Chatroom2() {
  const [solution_0, set_solution_0] = useState<Solution>();
  const [solution_1, set_solution_1] = useState<Solution>();
  useEffect(() => {
    if (!solution_0) return;
    if (!solution_1) return;

    const ccc = new DemoCallbacks([solution_0, solution_1]);
    solution_0.callbacks.add(ccc)
    solution_1.callbacks.add(ccc)
    return () => {
      solution_0.callbacks.delete(ccc)
      solution_1.callbacks.delete(ccc)
    }

  }, [solution_0, solution_1])

  return (
    <Chatroom onSolutionChanged={set_solution_1} />
  )
  // return (
  //   <div className={styles.demo_chatroom_view}>
  //     <Chatroom className={styles.left_chatroom} onSolutionChanged={set_solution_0} />
  //     <Chatroom className={styles.right_chatroom} onSolutionChanged={set_solution_1} />
  //   </div>
  // )
}